// 版权所有2009 Go作者。版权所有。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package ssagen

import (
	"fmt"
	"internal/buildcfg"
	"io/ioutil"
	"log"
	"os"
	"strings"

	"cmd/compile/internal/base"
	"cmd/compile/internal/ir"
	"cmd/compile/internal/staticdata"
	"cmd/compile/internal/typecheck"
	"cmd/compile/internal/types"
	"cmd/internal/obj"
	"cmd/internal/objabi"
)

// SymABIs记录汇编程序提供的有关symbol 
// 定义ABIs和参考ABIs的信息。
type SymABIs struct {
	defs map[string]obj.ABI
	refs map[string]obj.ABISet

	localPrefix string
}

func NewSymABIs(myimportpath string) *SymABIs {
	var localPrefix string
	if myimportpath != "" {
		localPrefix = objabi.PathToPrefix(myimportpath) + "."
	}

	return &SymABIs{
		defs:        make(map[string]obj.ABI),
		refs:        make(map[string]obj.ABISet),
		localPrefix: localPrefix,
	}
}

// canonicalize返回在
// s的映射中用于链接器符号的规范名称。本文件包中的符号可以写为“”。X或
// 且包的导入路径已在符号中。这将
// 都改写为“.”，匹配编译器生成的链接器符号名称。
func (s *SymABIs) canonicalize(linksym string) string {
	// 如果符号的前缀已经是localPrefix，则
	// 将其重写为以“”开头，使其与
	// 编译器的内部符号名称匹配。
	if s.localPrefix != "" && strings.HasPrefix(linksym, s.localPrefix) {
		return `"".` + linksym[len(s.localPrefix):]
	}
	return linksym
}

// ReadSymABIs读取指定定义的symabis文件，以及ABI对文本符号的引用。
// 
// symabis格式是一组行，其中每一行是一个序列
// 由空格分隔的字段组成。第一个字段是动词，是
// “def”用于定义符号ABI，或“ref”用于引用
// 。对于“def”和“ref”，第二个字段是
// 符号名称，第三个字段是ABI名称，作为
// 命名的cmd/internal/obj之一。ABI常数。基于
func (s *SymABIs) ReadSymABIs(file string) {
	data, err := ioutil.ReadFile(file)
	if err != nil {
		log.Fatalf("-symabis: %v", err)
	}

	for lineNum, line := range strings.Split(string(data), "\n") {
		lineNum++ // 1的
		line = strings.TrimSpace(line)
		if line == "" || strings.HasPrefix(line, "#") {
			continue
		}

		parts := strings.Fields(line)
		switch parts[0] {
		case "def", "ref":
			// 解析行。
			if len(parts) != 3 {
				log.Fatalf(`%s:%d: invalid symabi: syntax is "%s sym abi"`, file, lineNum, parts[0])
			}
			sym, abistr := parts[1], parts[2]
			abi, valid := obj.ParseABI(abistr)
			if !valid {
				log.Fatalf(`%s:%d: invalid symabi: unknown abi "%s"`, file, lineNum, abistr)
			}

			sym = s.canonicalize(sym)

			// 稍后再录制。ABI 
			if parts[0] == "def" {
				s.defs[sym] = abi
			} else {
				s.refs[sym] |= obj.ABISetOf(abi)
			}
		default:
			log.Fatalf(`%s:%d: invalid symabi type "%s"`, file, lineNum, parts[0])
		}
	}
}

// GenABIWrappers将ABI信息应用于Funcs，并在必要时生成ABI 
// wrapper函数。
func (s *SymABIs) GenABIWrappers() {
	// 对于cgo导出的符号，我们告诉链接器将
	// 定义ABI导出到C。这也意味着，即使有链接名，我们也不想创建ABI包装。
	// 
	// TODO（奥斯汀）：也许我们想创建ABI包装器，但
	// 确保链接器在
	// 未混合名称下导出正确的ABI定义？
	cgoExports := make(map[string][]*[]string)
	for i, prag := range typecheck.Target.CgoPragmas {
		switch prag[0] {
		case "cgo_export_static", "cgo_export_dynamic":
			symName := s.canonicalize(prag[1])
			pprag := &typecheck.Target.CgoPragmas[i]
			cgoExports[symName] = append(cgoExports[symName], pprag)
		}
	}

	// 将ABI DEF和REF应用于Funcs并生成包装器。
	// 
	// 这可能会为包装器生成新的DECL，但我们
	// 特别地*不*想访问这些，以免我们为包装器创建
	// 包装器。
	for _, fn := range typecheck.Target.Decls {
		if fn.Op() != ir.ODCLFUNC {
			continue
		}
		fn := fn.(*ir.Func)
		nam := fn.Nname
		if ir.IsBlank(nam) {
			continue
		}
		sym := nam.Sym()
		var symName string
		if sym.Linkname != "" {
			symName = s.canonicalize(sym.Linkname)
		} else {
			// 这些名字已经是规范的了。
			symName = sym.Pkg.Prefix + "." + sym.Name
		}

		// 应用定义。
		defABI, hasDefABI := s.defs[symName]
		if hasDefABI {
			if len(fn.Body) != 0 {
				base.ErrorfAt(fn.Pos(), "%v defined in both Go and assembly", fn)
			}
			fn.ABI = defABI
		}

		if fn.Pragma&ir.CgoUnsafeArgs != 0 {
			// CgoUnsafeArgs表示函数（或其被调用方）使用
			// 偏移量来分派参数，当前使用ABI0 
			// 帧布局。把它固定在ABI0上。
			fn.ABI = obj.ABI0
		}

		// 如果cgo已导出，请将定义ABI添加到cgo 
		// pragmas。
		cgoExport := cgoExports[symName]
		for _, pprag := range cgoExport {
			// export pragmas的格式为：
			// 
			// cgo_export.*<local>[<remote>]
			// 
			// 如果省略<remote>，则与
			// /<local>相同。
			// 
			// 扩展到
			// 
			// cgo_导出*<local><remote><ABI>
			if len(*pprag) == 2 {
				*pprag = append(*pprag, (*pprag)[1])
			}
			// 添加ABI参数。
			*pprag = append(*pprag, fn.ABI.String())
		}

		// 应用引用。
		if abis, ok := s.refs[symName]; ok {
			fn.ABIRefs |= abis
		}
		// 假设所有函数都至少被引用为
		// ABIInteral，因为它们可能被引用自
		// 其他包。
		fn.ABIRefs.Set(obj.ABIInternal, true)

		// 如果在这个包中定义了一个符号（无论是在
		// Go还是assembly中），并且给出了一个链接名，那么它可能是从另一个包引用的
		// 所以让它可以通过任何ABI调用。重要的是我们知道
		// 它是在这个包中定义的，因为其他包
		// 可能会使用linkname“拉”符号，我们不希望
		// 创建重复的ABI包装。
		// 
		// 但是，如果它提供了导出到
		// C的链接名，那么我们不制作ABI包装，因为cgo 
		// 工具需要原始定义。
		hasBody := len(fn.Body) != 0
		if sym.Linkname != "" && (hasBody || hasDefABI) && len(cgoExport) == 0 {
			fn.ABIRefs |= obj.ABISetCallable
		}

		// 再次检查cgo导出的符号是否没有任何包装。
		if len(cgoExport) > 0 && fn.ABIRefs&^obj.ABISetOf(fn.ABI) != 0 {
			base.Fatalf("cgo exported function %s cannot have ABI wrappers", fn)
		}

		if !buildcfg.Experiment.RegabiWrappers {
			continue
		}

		forEachWrapperABI(fn, makeABIWrapper)
	}
}

// InitLSym定义了f的对象。LSym，并根据f的
// 属性对其进行初始化。这包括设置符号标志以及ABI和
// 创建和初始化相关的矮人符号。
// 
// InitLSym必须为每个函数调用一次，并且必须为
// 对于带实体的函数和不带实体的函数都被调用。
// 对于无实体函数，我们只创建LSym；对于带有实体的函数
// 调用帮助程序来设置/填充LSym。
func InitLSym(f *ir.Func, hasBody bool) {
	if f.LSym != nil {
		base.FatalfAt(f.Pos(), "InitLSym called twice on %v", f)
	}

	if nam := f.Nname; !ir.IsBlank(nam) {
		f.LSym = nam.LinksymABI(f.ABI)
		if f.Pragma&ir.Systemstack != 0 {
			f.LSym.Set(obj.AttrCFunc, true)
		}
		if f.ABI == obj.ABIInternal || !buildcfg.Experiment.RegabiWrappers {
			// 函数值只能指向
			// funcsym。
			// 函数或其包装器（视情况而定）创建
			// 
			// 如果我们不使用ABI包装器，我们只使用
			// InitLSym来定义函数的ABI，
			// 所以当我们看到它时，我们创建funcsym。
			staticdata.NeedFuncSym(f)
		}
	}
	if hasBody {
		setupTextLSym(f, 0)
	}
}

func forEachWrapperABI(fn *ir.Func, cb func(fn *ir.Func, wrapperABI obj.ABI)) {
	need := fn.ABIRefs &^ obj.ABISetOf(fn.ABI)
	if need == 0 {
		return
	}

	for wrapperABI := obj.ABI(0); wrapperABI < obj.ABICount; wrapperABI++ {
		if !need.Get(wrapperABI) {
			continue
		}
		cb(fn, wrapperABI)
	}
}

// makeABIWrapper创建一个新函数，将使用
// wrapperABI调用该函数，并使用f.ABI调用“f”。
func makeABIWrapper(f *ir.Func, wrapperABI obj.ABI) {
	if base.Debug.ABIWrap != 0 {
		fmt.Fprintf(os.Stderr, "=-= %v to %v wrapper for %v\n", wrapperABI, f.ABI, f)
	}

	// Q：需要这个吗？
	savepos := base.Pos
	savedclcontext := typecheck.DeclContext
	savedcurfn := ir.CurFunc

	base.Pos = base.AutogeneratedPos
	typecheck.DeclContext = ir.PEXTERN

	// 目前我们不支持包装方法，我们需要下面的机器来处理接收器。如果我们看到这种情况，就会感到恐慌。
	ft := f.Nname.Type()
	if ft.NumRecvs() != 0 {
		panic("makeABIWrapper support for wrapping methods not implemented")
	}

	// 为包装器制作一个新的func类型。
	var noReceiver *ir.Field
	tfn := ir.NewFuncType(base.Pos,
		noReceiver,
		typecheck.NewFuncParams(ft.Params(), true),
		typecheck.NewFuncParams(ft.Results(), false))

	// 重用f的类型。Sym创建一个新的ODCLFUNC/函数。
	fn := typecheck.DeclFunc(f.Nname.Sym(), tfn)
	fn.ABI = wrapperABI

	fn.SetABIWrapper(true)
	fn.SetDupok(true)

	// ABI0到ABI内部包装器将主要将参数从
	// 堆栈加载到寄存器（和/或在包装调用后将堆栈位置存储回
	// 寄存器）；在大多数情况下，它们不需要分配堆栈空间，因此在这些情况下可以将它们标记为NOSPLIT。此外，我的假设是，在大多数（但不是所有）
	// 情况下，用汇编编写的函数都不存在。如果一个ABIInternal目标有太多的
	// 参数要放入寄存器，包装器将需要
	// 分配堆栈空间，但这似乎是不太可能的情况。
	// 因此：请将这些包装纸标记为不开裂。另一方面，
	// 
	// ABIInternal-to-ABI0包装器将在
	// 寄存器中的内容放入堆栈，这意味着它们将始终需要分配
	// ABI0调用之前，将
	// 堆栈空间。如果编译器将它们标记为NOSPLIT，这似乎会导致链接器的NOSPLIT溢出分析触发链接失败。另一方面，如果他们没有标记NOSPLIT，那么在构建运行时可能会导致问题（因为在堆栈增长不安全的情况下，可能会调用
	// asm例程）。在
	// 大多数情况下，包装器（实际上）是内联的，但是
	// 运行时的间接调用（可能）可能会在这里遇到麻烦。
	// 修复我：目前所有。当我省略
	// NOSPLIT作为这些包装时，bash不会通过，所以目前所有包装都被标记为NOSPLIT。
	fn.Pragma |= ir.Nosplit

	// 生成呼叫。如果没有参数也没有返回，则使用tail call，否则使用常规调用。
	// 
	// 注意：理想情况下，如果
	// 有参数，但ABI0->ABIInternal wrappers没有返回，
	// 只要所有参数都适合寄存器（例如，我们没有
	// 来分配任何堆栈空间），我们就可以使用尾部调用。这样做需要在typecheck/walk/ssa中做一些额外的工作，可能需要添加一个新节点
	// OTAILCALL或类似的内容。
	tailcall := tfn.Type().NumResults() == 0 && tfn.Type().NumParams() == 0 && tfn.Type().NumRecvs() == 0
	if base.Ctxt.Arch.Name == "ppc64le" && base.Ctxt.Flag_dynlink {
		// 无法使用动态链接对PPC64进行tailcall，因为我们需要
		// 在调用后恢复R2。
		tailcall = false
	}
	if base.Ctxt.Arch.Name == "amd64" && wrapperABI == obj.ABIInternal {
		// 无法在AMD64上从ABIInteral向ABI0进行尾调用，因为返回ABIInteral时需要
		// 向特殊寄存器（X15）进行尾调用。
		tailcall = false
	}

	var tail ir.Node
	call := ir.NewCallExpr(base.Pos, ir.OCALL, f.Nname, nil)
	call.Args = ir.ParamNames(tfn.Type())
	call.IsDDD = tfn.Type().IsVariadic()
	tail = call
	if tailcall {
		tail = ir.NewTailCallStmt(base.Pos, call)
	} else if tfn.Type().NumResults() > 0 {
		n := ir.NewReturnStmt(base.Pos, nil)
		n.Results = []ir.Node{call}
		tail = n
	}
	fn.Body.Append(tail)

	typecheck.FinishFuncBody()
	if base.Debug.DclStack != 0 {
		types.CheckDclstack()
	}

	typecheck.Func(fn)
	ir.CurFunc = fn
	typecheck.Stmts(fn.Body)

	typecheck.Target.Decls = append(typecheck.Target.Decls, fn)

	// 恢复以前的上下文。
	base.Pos = savepos
	typecheck.DeclContext = savedclcontext
	ir.CurFunc = savedcurfn
}

// setupTextLsym为带有正文的符号初始化LSym。
func setupTextLSym(f *ir.Func, flag int) {
	if f.Dupok() {
		flag |= obj.DUPOK
	}
	if f.Wrapper() {
		flag |= obj.WRAPPER
	}
	if f.ABIWrapper() {
		flag |= obj.ABIWRAPPER
	}
	if f.Needctxt() {
		flag |= obj.NEEDCTXT
	}
	if f.Pragma&ir.Nosplit != 0 {
		flag |= obj.NOSPLIT
	}
	if f.ReflectMethod() {
		flag |= obj.REFLECTMETHOD
	}

	// 笨拙但重要。
	// 对于可能正在调用延迟
	// 可以恢复的函数（runtime.reflectcall、reflect.callReflect、
	// 和reflect.callMethod），我们需要panic+recover特殊处理。
	// 请参阅测试/恢复。选择测试用例和src/reflect/value。请点击
	// 了解正在考虑的实际功能。
	// 
	// 运行时。reflectcall是一个汇编函数，它可以调用
	// 包装函数（runtime.callNN）。它的ABI包装器也需要包装器
	// 标志。
	fnname := f.Sym().Name
	if base.Ctxt.Pkgpath == "runtime" && fnname == "reflectcall" {
		flag |= obj.WRAPPER
	} else if base.Ctxt.Pkgpath == "reflect" {
		switch fnname {
		case "callReflect", "callMethod":
			flag |= obj.WRAPPER
		}
	}

	base.Ctxt.InitTextSym(f.LSym, flag)
}
